/*
 * CHttpClient.cpp
 *
 *  Created on: 2017年3月31日
 *      Author: chuanjiang.zh
 */

#include "CHttpClient.h"
#include "TStringUtil.h"
#include "HttpUtil.h"


static void ev_handler(struct mg_connection *nc, int ev, void *ev_data)
{
	CHttpClient* client = (CHttpClient*)(nc->mgr->user_data);
	client->handleEvent(nc, ev, ev_data);
}

CHttpClient::CHttpClient():
    m_mgr(),
	m_timeout(TIMEOUT)
{
	mg_mgr_init(&m_mgr, NULL);

    m_mgr.user_data = this;

	setStackSize(1024 * 1024 * 2);
}

CHttpClient::~CHttpClient()
{
	close();

	mg_mgr_free(&m_mgr);
}

bool CHttpClient::request(const std::string& url, const std::string& httpMethod, const StringMap& headers, const std::string& body,
	HttpClientCallback cb)
{
    HttpContext context;
    context.cb = cb;

	std::string extra_headers;
	for (StringMap::const_iterator it = headers.begin(); it != headers.end(); ++ it)
	{
		extra_headers += it->first;
		extra_headers += ": ";
		extra_headers += it->second;
		extra_headers += "\r\n";
	}

    const char *post_data = (httpMethod == "GET") ? NULL : body.c_str();
	//context.connection = mg_connect_http(&m_mgr, ev_handler, url.c_str(), extra_headers.c_str(), post_data);
	context.connection = mg_connect_http_ex(&m_mgr, ev_handler, url.c_str(), httpMethod.c_str(), extra_headers.c_str(), post_data);

	if (!context.connection)
	{
		return false;
	}

	if (m_timeout > 0)
	{
		mg_set_timer(context.connection, mg_time() + ((double)m_timeout)/1000);
	}

    m_contextMap.put(context.connection, context);

    if (!isRunning())
    {
        start();
    }
	else
	{
		breakPoll();
	}

	return context.connection != NULL;
}

void CHttpClient::close()
{
	if (isRunning())
	{
		stop();
	}
}

bool CHttpClient::isOpen()
{
	return isRunning();
}

int CHttpClient::run()
{
	while (!m_canExit)
	{
		mg_mgr_poll(&m_mgr, 1000 * 30);
	}
	return 0;
}

static void ev_null_handler(struct mg_connection *, int ev, void *)
{
    // pass
}

void CHttpClient::doStop()
{
	breakPoll();
}

void CHttpClient::breakPoll()
{
	if (comn::Thread::getCurrentThreadId() == getThreadID())
	{
		return;
	}

	int ctrl = 0;
	mg_broadcast(&m_mgr, ev_null_handler, &ctrl, sizeof(ctrl));
}

void CHttpClient::handleEvent(struct mg_connection *nc, int ev, void *ev_data)
{
	struct http_message *hm = (struct http_message *) ev_data;
	
	//printf("CHttpClient::handleEvent. ev:%d\n", ev);

	switch (ev)
	{
		case MG_EV_CONNECT:
		{
			int code = *((int *)ev_data);
			if (code != 0)
			{
                nc->flags |= MG_F_CLOSE_IMMEDIATELY;

				const char* text = strerror(code);
				fireCallback(nc, -ECONNREFUSED, text, strlen(text), nullptr);
			}
			break;
		}
		case MG_EV_RECV:
		{
			//int num_bytes = *((int*)ev_data);
			//printf("recv. total:%d, len:%d\n", 0, num_bytes);
			break;
		}
		case MG_EV_HTTP_REPLY:
		{
			nc->flags |= MG_F_CLOSE_IMMEDIATELY;
            
            fireCallback(nc, hm->resp_code, hm->body.p, hm->body.len, hm);
            
			break;
		}
        case MG_EV_CLOSE:
        {
            int code = EBADF;
			parseStatusCode(nc->recv_mbuf.buf, nc->recv_mbuf.len, code);

            fireCallback(nc, code, nc->recv_mbuf.buf, nc->recv_mbuf.len, hm);
            break;
        }
		case MG_EV_TIMER:
		{
			int code = ETIMEDOUT;
			parseStatusCode(nc->recv_mbuf.buf, nc->recv_mbuf.len, code);
			fireCallback(nc, code, nc->recv_mbuf.buf, nc->recv_mbuf.len, hm);
			nc->flags |= MG_F_CLOSE_IMMEDIATELY;
			break;
		}
		case MG_EV_HTTP_CHUNK:
		{
			//struct mg_http_proto_data* pd = (struct mg_http_proto_data*)nc->proto_data;

			if ((nc->flags & MG_F_CHUNK_ZERO) != 0)
			{
				printf("MG_EV_HTTP_CHUNK. len: %d, flags: %d\n", (int)hm->body.len, nc->flags);
			}
			break;
		}
		default:
		{
			break;
		}
	}
}

void CHttpClient::fireCallback(HttpContext& context, int code, const char* data, size_t length, const http_message* msg)
{
	//printf("CHttpClient::fireCallback\n");

	if (context.cb)
	{
        context.cb(code, data, length, msg);
	}

}

bool CHttpClient::fireCallback(struct mg_connection *nc, int code, const char* data, size_t length, const http_message* msg)
{
    HttpContext context;
    if (!m_contextMap.remove(nc, context))
    {
        return false;
    }

    fireCallback(context, code, data, length, msg);
    return true;
}

void CHttpClient::setTimeout(int seconds)
{
	m_timeout = seconds * 1000;
}

bool CHttpClient::parseStatusCode(const char* data, size_t size, int& code)
{
	bool got = false;
	if (data && size > 0)
	{
		std::string line;
		for (size_t i = 0; i < size; ++i)
		{
			if (data[i] == '\n' || data[i] == '\r')
			{
				line.assign(data, i);
				break;
			}
		}

		std::string proto;
		std::string statusCode;
		comn::StringUtil::split(line, ' ', proto, statusCode);
		if (statusCode.size() > 0)
		{
			comn::StringCast::toValue(statusCode, code);
			got = true;
		}
	}
	return got;
}
